<template>
	<div class="md-form-modal-item" :class="classes" :style="{textAlign: align}">
		<div class="md-form-body">
			<div class="md-form-label" v-if="hasLabel" :class="{'is-required': isRequired, 'required-position-right': reqPosition==='right'}" :style="style">
				<slot name="label" v-bind:label="label">
					{{ label }}
				</slot>
			</div>
			<div class="md-form-input">
				<div class="_input">
					<slot/>
				</div>
				<div class="_error" v-show="isShowError || errorMsg">
					<slot name="error" v-if="isShowError">
						<div class="md-errors">
							<div class="md-form-error" :class="{'md-form-error-inline': inlineError || mdForm.inlineError}">
								<transition-group tag="ul" name="slide-fade" mode="out-in">
									<template v-if="oneError && errorMsg && errorMsg.length > 0">
										<li key="0ne">{{ errorMsg[0] }}</li>
									</template>
									<template v-else>
										<li v-for="(e) in errorMsg" :key="e">{{ e }}</li>
									</template>
								</transition-group>
							</div>
						</div>
					</slot>
					<slot name="help">
						<p class="md-help-msg" v-show="errorMsg && errorMsg.length <= 0 && helpMsg">{{ helpMsg }}</p>
					</slot>
				</div>
				<slot name="end"></slot>
			</div>
		</div>
	</div>
</template>

<script>
import validator from 'el-form-validator';
import GConfig from '../common/config';

const getPropByPath = (obj, path, strict) => {
	let tempObj = obj;
	path = path.replace(/\[(\w+)\]/g, '.$1');
	path = path.replace(/^\./, '');

	let keyArr = path.split('.');
	let i = 0;
	for (let len = keyArr.length; i < len - 1; ++i) {
		if (!tempObj && !strict) break;
		let key = keyArr[i];
		if (key in tempObj) {
			tempObj = tempObj[key];
		} else {
			if (strict) {
				throw new Error('please transfer a valid prop path to form item!:' + path);
			}
			break;
		}
	}
	return {
		o: tempObj,
		k: keyArr[i],
		v: tempObj ? tempObj[keyArr[i]] : null
	};
};

export default {
	name: 'MdFormModuleItem',
	inject: {
		mdForm: {
			value: 'mdForm', default: () => {
				return {};
			}
		}
	},
	props: {
		prop: String,
		label: String,
		labelWidth: String,
		labelPosition: String,
		requiredPosition: String,
		align: String,
		rules: [Array, String],
		fieldName: Object,
		showOneError: Boolean,
		inlineError: Boolean,
		inline: Boolean | Number,
		showError: Boolean | Number,
		error: {
			type: Array,
			default: () => []
		},
		errorCustomMessages: Object, // 根据不同的规则自定义错误消息,
		errMessage: String,
		helpMsg: String
	},
	data() {
		return {
			errorMsg: this.error
		};
	},
	mounted() {
		this.mdForm.addFormItem(this);
		setTimeout(() => {
			this.addEvent();
		}, 1000);
	},
	beforeDestroy() {
		this.mdForm.removeFormItem(this);
	},
	computed: {
		hasLabel() {
			return this.label !== undefined || this.$slots.label;
		},
		reqPosition() {
			return this.requiredPosition || this.mdForm.requiredPosition || 'left';
		},
		isRequired() {
			let rules = this.getRules();
			if (!this.prop || !rules) return false;
			if (typeof rules === 'string')
				return rules.indexOf('required') && this.mdForm.showRequiredTag;
			if (Array.isArray(rules)) {
				let isReq = false;
				rules.forEach(rule => {
					if (typeof rule === 'object' && rule.toString() === '[object Object]') {
						if (rule.type === 'required')
							isReq = true;
					} else if (rule === 'required')
						isReq = true;
				});
				return isReq && this.mdForm.showRequiredTag;
			}
			return rules && (Array.isArray(rules) || typeof rules === 'string') && rules.indexOf('required') !== -1 && this.mdForm.showRequiredTag;
		},
		hasError() {
			return this.errorMsg && this.errorMsg.length > 0;
		},
		classes() {
			return {
				'is-error': this.hasError,
				'md-form-inline': (this.inline === undefined) ? this.mdForm.inline : this.inline,
				'md-form-label-width': (!!this.mdForm.labelWidth || this.labelWidth) && (this.mdForm.inline || this.inline),
				'md-hidden': this.hidden
			};
		},
		style() {
			let labelWidth = this.labelWidth || this.mdForm.labelWidth;
			return {
				flex: '0 0 ' + labelWidth,
				width: labelWidth,
				textAlign: this.labelPosition || this.mdForm.labelPosition
			};
		},
		isShowError() {
			if (this.showError !== undefined) return this.showError;
			if (this.mdForm.showError !== undefined) return this.mdForm.showError;
			return true;
		},
		oneError() {
			return this.mdForm.showOneError || this.showOneError;
		}
	},
	created() {
	},
	methods: {
		async validator(isTrack = false) {
			let rules = this.getRules();
			let customMessage = {};
			let customRule = [];

			if (Array.isArray(rules)) {
				let _temp = [];
				rules.forEach(rule => {
					if (typeof rule === 'object' && rule.toString() === '[object Object]') {
						if (rule.validator instanceof Function) {
							customRule.push(rule);
						} else {
							if (rule.params) {
								_temp.push(`${rule.type}:${rule.params}`);
							} else {
								_temp.push(rule.type);
							}
							customMessage[rule.type] = rule.message;
						}
					} else {
						_temp.push(rule);
					}
				});
				rules = _temp;
			}

			if (!rules || !this.prop) {
				return true;
			}
			let data = {};
			let prop = this.prop.split('.')
			if (prop.length > 1) {
				prop = prop[prop.length - 1];
			} else {
				prop = prop[0];
			}
			data[prop] = this.getValue();
			let rule = {};
			rule[prop] = rules;

			if (rules.indexOf('confirmed') !== -1) {
				data[prop + '_confirmation'] = this.mdForm.model[prop + '_confirmation'];
			}
			let v = validator.make(data, rule, this.getCustomMessages(customMessage), this.fieldName, this.callbackMessage);
			let fails = v.fails();
			if (fails) {
				if (!this.errMessage) {
					this.errorMsg = v.getErrors()[prop];
				} else {
					this.errorMsg = [this.errMessage];
				}
			} else {
				this.errorMsg = [];
			}

			let errorMessage = [];
			customRule.forEach(rule => {
				if (!rule.validator({data: this.mdForm.model, value: this.getValue()})) {
					errorMessage.push(rule.message);
					fails = true;
				}
			});
			if (fails) {
				if (isTrack)
					this.$el.scrollIntoView({behavior: 'smooth', block: 'end', inline: 'nearest'});
				if (!this.errMessage) {
					this.errorMsg.push(...errorMessage);
				} else {
					this.errorMsg = [this.errMessage];
				}
			} else {
				this.errorMsg = [];
			}
			return !fails;
		},
		addEvent() {
			if (this.mdForm && this.mdForm.model && this.prop && this.getRules()) {
				this.$watch(`mdForm.model.${this.prop}`, () => {
					this.validator();
				});
			}
		},
		getRules() {
			return this.rules ? this.rules : (this.mdForm.rules ? this.mdForm.rules[this.prop] : {});
		},
		getValue() {
			return getPropByPath(this.mdForm.model, this.prop, true).v;
		},
		callbackMessage(message, params) {
			if ([undefined, null].includes(message)) {
				message = '';
			}
			let hookErrorMessage = GConfig.errorMessageHook(message, this.prop, params);
			if (hookErrorMessage !== undefined) {
				return hookErrorMessage;
			}
			return message;
		},
		reset() {
			this.errorMsg = [];
		},
		getCustomMessages(customMessage) {
			return {...this.errorCustomMessages, ...customMessage}
		}
	},
	filters: {},
	watch: {
		error() {
			this.$nextTick(() => {
				this.errorMsg = this.error;
			});
		}
	}
};
</script>

<style scoped lang="scss">
.md-form-modal-item {
	position: relative;
	width: 100%;
	vertical-align: top;
	margin-bottom: 10px;

	&.is-error {
		> .md-form-body {
			> .md-form-input {
				> ._input {
					::v-deep {
						textarea,
						input {
							border-color: red;

							&:hover, &:focus, &:focus-visible {
								outline-color: red;
								border-color: red;
							}

							&:focus {
								outline: none;
								box-shadow: 0 0 2px 2px rgba(234, 25, 25, .5);
							}
						}
					}
				}
			}
		}
	}

	&.md-form-inline {
		> .md-form-body {
			display: flex;

			.md-form-label {
				margin-right: 10px;
			}

			.md-form-input {
				flex: 1;
			}
		}
	}

	&.md-form-label-width {

	}

	&.md-hidden {
		display: none;
	}

	> .md-form-body {
		&.md-form-block {
			display: block !important;
			width: 100%;
		}

		.md-form-label {
			font-size: 14px;
			line-height: 32px;

			&.is-required {
				&:before {
					content: '*';
					color: red;
				}
			}

			&.is-required.required-position-right {
				&:before {
					display: none;
				}

				&:after {
					content: '*';
					color: red;
				}
			}
		}

		.md-form-input {
			line-height: 32px;
			position: relative;

			._input {
				box-sizing: border-box;
			}

			._error {
				.md-errors {
					.md-form-error {
						&.md-form-error-inline {
							position: absolute;
							right: 5px;
							top: 2px;
						}

						ul {
							list-style: none;
							margin: 0;
							padding: 0;
							font-size: 12px;
							color: red;

							li {
								line-height: 20px;
							}
						}
					}
				}

				.md-help-msg {
					font-size: 12px;
					margin: 0;
					line-height: 20px;
					text-align: right;
				}
			}
		}
	}
}
</style>
